namespace cpputils
{

class serializer;

class serializable : public virtual virtual_object
{
public:
	virtual void serialize(serializer& Serializer) = 0;
};

class serializer : public virtual virtual_object, private noncopyable
{
public:
	struct property
	{
	public:
		property(serializer* SetSerializer);
		~property();
		serializer& get_serializer() const;
		template<class T>
		void operator()(T& V) const
		{
			assert(!Serialized);
			Serializer->serialize_object(V);
			Serialized = true;
		}
		template<class T>
		void get(T& V) const
		{
			assert(Serializer->is_loading());
			operator()(V);
		}
		template<class T>
		T get() const
		{
			T V;
			get(V);
			return V;
		}
		template<class T>
		void put(const T& V) const
		{
			assert(!Serializer->is_loading());
			operator()(to_nonconst(V));
		}
		operator serializer&() const;

	private:
		serializer* Serializer;
		mutable bool Serialized, IsCompound;
	};

	serializer();
	const class_factory* get_class_factory() const;
	void set_class_factory(const class_factory* Val);
	virtual bool is_loading() const = 0;
	bool is_saving() const;
	void begin_property(const string& Name = "");
	void end_property();
	property operator[](const string& Name)
	{
		new_property(Name);
		return property(this);
	}
	template<class T>
	serializer& operator()(T& Val)
	{
		return operator()("", Val);
	}
	template<class T>
	serializer& operator()(const string& Name, T& Val)
	{
		new_property(Name);
		serialize_object(Val);
		return *this;
	}

protected:
	virtual void new_property(const string& Name) = 0;
	virtual void begin_compound() = 0;
	virtual void end_compound() = 0;

	template<class T>
	void serialize_object(T& V)
	{
		begin_compound();
		serialize(*this, V);
		end_compound();
	}
	virtual void serialize_object(bool& V) = 0;
	virtual void serialize_object(int& V) = 0;
	virtual void serialize_object(unsigned int& V) = 0;
	virtual void serialize_object(short& V) = 0;
	virtual void serialize_object(unsigned short& V) = 0;
	virtual void serialize_object(long& V) = 0;
	virtual void serialize_object(unsigned long& V) = 0;
	virtual void serialize_object(long long& V) = 0;
	virtual void serialize_object(unsigned long long& V) = 0;
	virtual void serialize_object(float& V) = 0;
	virtual void serialize_object(double& V) = 0;
	virtual void serialize_object(string& V) = 0;

private:
	const class_factory* ClassFactory;
};

template<class T>
void serialize(serializer& Serializer, T& V)
{
	V.serialize(Serializer);
}

template<class T>
void serialize(serializer& Serializer, subarray<T>& V)
{
	for (int I = 0; I < V.size(); I++) Serializer(V[I]);
}

template<class T>
void serialize_array_size(const serializer::property& Prop, T& V)
{
	if (Prop.get_serializer().is_loading())
	{
		V.clear();
		V.resize(Prop.get<int>());
	}
	else
		Prop.put((int)V.size());
}

template<class T>
void serialize(serializer& Serializer, vector<T>& V)
{
	serialize_array_size(Serializer["Size"], V);
	Serializer("Data", to_nonconst(make_subarray(V)));
}

template<class T>
void serialize(serializer& Serializer, array<T>& V)
{
	serialize_array_size(Serializer["Size"], V);
	Serializer("Data", to_nonconst(make_subarray(V)));
}

template<class T>
void begin_array_property(serializer& Serializer,const string& Name, T& V)
{
	Serializer.begin_property(Name);
	serialize_array_size(Serializer["Size"], V);
	Serializer.begin_property("Data");
}

inline void end_array_property(serializer& Serializer)
{
	Serializer.end_property();
	Serializer.end_property();
}

namespace serializer_impl
{
	template<class T>
	void initialize_dynamic_object(serializer& Serializer, smart_ptr<T>& V, void*)
	{
		if (Serializer.is_loading()) V.reset(new T);
	}
	template<class T>
	void initialize_dynamic_object(serializer& Serializer, smart_ptr<T>& V, serializable*)
	{
		if (Serializer.is_loading())
		{
			string ClassName = Serializer["Class"].get<string>();
			V = dynamic_convert<T>(Serializer.get_class_factory()->make(ClassName));
		}
		else
			Serializer["Class"].put(Serializer.get_class_factory()->get_name(typeid(*V)));
	}
	template<class T>
	void initialize_dynamic_object(serializer& Serializer, smart_ptr<T>& V)
	{
		if (Serializer.is_loading())
		{
			V.reset();
			if (Serializer["Null"].get<bool>()) return;
		}
		else
		{
			Serializer["Null"].put(V.get() == 0);
			if (V.get() == 0) return;
		}
		initialize_dynamic_object(Serializer, V, &*V);
	}
}

template<class T>
void serialize(serializer& Serializer, smart_ptr<T>& V)
{
	serializer_impl::initialize_dynamic_object(Serializer, V);
	if (V.get() != 0) Serializer("Data", *V);
}

template<class T>
void begin_dynamic_object(serializer& Serializer, const string& Name, smart_ptr<T>& V)
{
	Serializer.begin_property(Name);
	serializer_impl::initialize_dynamic_object(Serializer, V);
	Serializer.begin_property("Data");
}

inline void end_dynamic_object(serializer& Serializer)
{
	Serializer.end_property();
	Serializer.end_property();
}

class text_serializer : public serializer
{
public:
	text_serializer();
	void load(const string& Filename);
	void save(const string& Filename);
	void close();
	bool is_loading() const;
	bool eof();

protected:
	void new_property(const string& Name);
	void begin_compound();
	void end_compound();
	void serialize_object(bool& V);
	void serialize_object(int& V);
	void serialize_object(unsigned int& V);
	void serialize_object(short& V);
	void serialize_object(unsigned short& V);
	void serialize_object(long& V);
	void serialize_object(unsigned long& V);
	void serialize_object(long long& V);
	void serialize_object(unsigned long long& V);
	void serialize_object(float& V);
	void serialize_object(double& V);
	void serialize_object(string& V);

private:
	smart_file File;
	file_formatter Fmt;
	bool First;
	int Indent;
};

}
